import java.text.*;
import java.util.*;
import java.io.*;
import edu.jhu.htm.core.Domain;
import edu.jhu.htm.core.HTMfunc;
import edu.jhu.htm.core.Vector3d;

public class PredictLocation {

	private enum ModelType { MM, MM2, MM3 , HMM, HMM2, HMM3, MMM };

	private static ModelType modelType = ModelType.HMM3;

	private static boolean single = true;

	private static int numStates = 10;

	private static int maxSequences = 50;

	private static int minimumLength = 2;

	private static int maximumLength = 25;
   
        private static double distanceThreshold = 100.0;

	private static int getCluster ( String timestamp ) {
		 String date = timestamp.substring(1, timestamp.indexOf(" "));
		 String time = timestamp.substring(timestamp.indexOf(" "));
		 Integer h = Integer.parseInt(time.trim().substring(0,2));
		 String data1[] = date.split("-");
		 Integer y = Integer.parseInt(data1[0]);
		 Integer m = Integer.parseInt(data1[1]);
		 Integer d = Integer.parseInt(data1[2]);
		 Calendar cal = Calendar.getInstance(); 
		 cal.set(y, m-1, d);
		 int dow = cal.get(Calendar.DAY_OF_WEEK);    
		 if ((dow >= cal.MONDAY && dow <= cal.FRIDAY && (h >=07 && h < 19))) return 1;
		 if ((dow >= cal.MONDAY && dow <= cal.FRIDAY && (h >=19 || h < 07)) || (dow == cal.SATURDAY && h < 07)) return 2;
		 return 3;
	 }

	 public static Map<Integer,List<List<MyObservationVector>>> readSequences ( File file ) throws IOException {
		int dimensionality = 2;	
		List<MyObservationVector> observationList = new ArrayList<MyObservationVector>();
		Map<Integer,List<List<MyObservationVector>>> results = new HashMap<Integer,List<List<MyObservationVector>>>();
		results.put(1,new ArrayList<List<MyObservationVector>>());
		results.put(2,new ArrayList<List<MyObservationVector>>());
		results.put(3,new ArrayList<List<MyObservationVector>>());
		BufferedReader r = new BufferedReader(new FileReader(file));
		String aux = null;
		String lastSequence = null;
		int y=0, m=0, d=0, h=0;
		Integer cluster = null;
		while ( (aux = r.readLine()) != null ) {
			aux = aux.trim();
			String data[] = aux.split(",");
			if ( single ) cluster = 1; else cluster = getCluster(data[5]);
			if ( lastSequence != null && !lastSequence.equals(data[0]) ) {
	      for ( int i = 0; i < observationList.size() - 1 ; ) {
	        MyObservationVector v1 = observationList.get(i);
	        MyObservationVector v2 = observationList.get(i+1);
	        if (v1.getTrixel().equals(v2.getTrixel())) observationList.remove(i); else i++;
	      }
			  if (observationList.size() > maximumLength && maximumLength > 0) {
			    while(observationList.size() > maximumLength) observationList.remove(0);
			  }
				if (observationList.size() > minimumLength || minimumLength < 0) {
					if ( results.get(cluster).size() < maxSequences && maxSequences > -1 ) results.get(cluster).add(observationList);
				}
				observationList = new ArrayList<MyObservationVector>();
				if(single) { 
					if ( results.get(1).size() >= maxSequences && maxSequences > -1 ) { r.close(); return results; }
				} else { 
					if ( results.get(1).size() >= maxSequences && 
					     results.get(2).size() >= maxSequences && 
					     results.get(3).size() >= maxSequences && maxSequences > -1 ) { r.close(); return results; }
			  }
			}
			double values[] = new double[dimensionality];		
		  for ( int i=1; i<dimensionality + 1; i++ ) {
				double value = new Double(data[i].trim().substring(data[i].trim().indexOf(":")+1));
				values[i-1] = value;
			}
			MyObservationVector auxV = new MyObservationVector(values);
			observationList.add(auxV);
			lastSequence = data[0];
		}
		if ( cluster != null && ( observationList.size() > minimumLength || minimumLength < 0) ) {
			if ( results.get(cluster).size() < maxSequences && maxSequences > -1 ) results.get(cluster).add(observationList);
		}
		r.close();
		return results;
	 }
	
	 public static void main(String[] args) throws IOException { 
	  int[] sequence_size= new int[3];
	  int[] numCorrect = new int[3];
	  int[] numCorrectTopFive = new int[3];
	  int[] numCorrectTopTen = new int[3];
	  int[] numPrev = new int[3];
		int[] numPrevCorrect = new int[3];
		int[] rankPos = new int[3];
		double[] reciprocal = new double[3];
		double[] distanceCorrect = new double[3];
		double[] distanceIncorrect = new double[3];
		double[] distanceOverall = new double[3];
		double[] Perplex= new double[3];
		for(int m =0; m<3; m++) {
			numCorrect[m]=0;
	     numCorrectTopFive[m]=0;
	    numCorrectTopTen[m]=0;
	    numPrev[m]=0;
			numPrevCorrect[m]=0;
			rankPos[m]=0;
			reciprocal[m]=0.0;
			distanceCorrect[m]=0.0;
			distanceIncorrect[m]=0.0;
			distanceOverall[m]=0.0;
			Perplex[m]=0.0;
		} 
		Map<Integer,List<List<MyObservationVector>>> obser = new HashMap<Integer,List<List<MyObservationVector>>>();
		obser.put(1,new ArrayList<List<MyObservationVector>>());
		obser.put(2,new ArrayList<List<MyObservationVector>>());
		obser.put(3,new ArrayList<List<MyObservationVector>>());
		Set<Object> esym = new HashSet<Object>();
                Map<Integer,List<List<MyObservationVector>>> seq = readSequences(new File(args.length == 0 ? "geolife.csv" : args[0]));
     MarkovModel mm1 []= new MarkovModel[seq.size()];
	  int iter = single ? 1 : seq.size();
    for ( int cluster=1; cluster<=iter; cluster++ ) {
		     Object[] state = new Object[numStates];
     		for ( int i = 0; i < state.length; i++) state[i] = "STATE" + i;
     		List<List<MyObservationVector>> obs =new ArrayList<List<MyObservationVector>>();
     		obs = seq.get(cluster);
		     Object[][] sequences = new Object[obs.size()][];
		     for ( int i = 0; i< obs.size(); i++) {
			    sequences[i] = new Object[obs.get(i).size()];
			    for ( int j = 0; j< obs.get(i).size(); j++) {
				    sequences[i][j] = obs.get(i).get(j);
				    esym.add(obs.get(i).get(j).getTrixel());
			    }	
		     }
		     Date date = new Date();
     		     System.out.println("Training of Markov Model for cluster "+ cluster + " started at " + date.toString() + " and used " + obs.size() + " sequences");    
		     switch ( modelType ) {
			case HMM  : mm1[cluster-1] = HMM.baumwelch( sequences, state, esym.toArray(new Object[0]), 0.0000001 ); break;
			case HMM2 : mm1[cluster-1] = SecondOrderHMM.baumwelch( sequences, state, esym.toArray(new Object[0]), 0.0000001 ); break;
			case HMM3 : mm1[cluster-1] = ThirdOrderHMM.baumwelch( sequences, state, esym.toArray(new Object[0]), 0.0000001 ); break;
			case MM   : mm1[cluster-1] = MM.train( sequences, esym.toArray(new Object[0])); break;
			case MM2  : mm1[cluster-1] = SecondOrderMM.train( sequences, esym.toArray(new Object[0])); break;
			case MM3  : mm1[cluster-1] = ThirdOrderMM.train( sequences, esym.toArray(new Object[0])); break;
		     }
 	  }
    obser = seq;
	  iter = single ? 1 : seq.size();
		for(int cluster=1; cluster<=iter; cluster++) {  	
		    List<List<MyObservationVector>> obs =new ArrayList<List<MyObservationVector>>();    
  		  obs=obser.get(cluster);
    		  Perplex[cluster-1] += perplexity(obs, mm1[cluster-1]);
		    for(int i=0;i< obs.size();i++) {
			    Object [] x = new Object[obs.get(i).size()];
			    for ( int j = 0; j< obs.get(i).size(); j++) { x[j] = obs.get(i).get(j); }
			    Map<Object,Double> map = predictNext(x, mm1[cluster-1]);
		    	String real = obs.get(i).get(x.length-1).getTrixel();
			    double max = Double.NEGATIVE_INFINITY;
			    Object top = "";
    			List<Object> locs = new ArrayList<Object>();
	    		locs.addAll(map.keySet());
	    		Collections.sort(locs, new ProbabilityComparator(map));
	    		int count = 1;
    			for ( Object loc : locs) {
	   	   		Double prob = map.get(loc);
	      			if ( loc.equals(real) ) {
			    	    System.out.print("Probability for real location " + real + " " + prob);
	    	  		  reciprocal[cluster-1] += 1.0 / count;
	    	  		  rankPos[cluster-1] += count;
	    	  		  if ( count <= 5 ) numCorrectTopFive[cluster-1]++;
	    	  		  if ( count <= 10 ) numCorrectTopTen[cluster-1]++;
	      			}
	      			if( prob > max ) { max = prob; top = loc; }
			        count++;
    			}
    			sequence_size[cluster-1]=obs.size();
    			double dist = obs.get(i).get(x.length-1).distance(top.toString());
    			distanceOverall[cluster-1]=distanceOverall[cluster-1]+dist;
 			    if ( (top.toString()).equals(real)) { 
  	      			System.out.println(" -- correct guessing"); 
  	     			numCorrect[cluster-1]++;
  	    			distanceCorrect[cluster-1]=distanceCorrect[cluster-1]+dist;
			        System.out.println("Distance correct " + top.toString() +  " : " + dist);
    			    } else {
  		  		distanceIncorrect[cluster-1]=distanceIncorrect[cluster-1]+dist;
    	 			System.out.println(" -- top location was " + top.toString() + " with probability " + max);
			        System.out.println("Distance incorrect " + top.toString() +  " : " + dist);
			    }
			    double[] evalues2 = new double[] {obs.get(i).get(x.length-1).value[0], obs.get(i).get(x.length-1).value[1]};  		
		    }
      		}
	 	if (!single) for(int m=0; m<3 ; m++) {
			switch(m) {
				case 0: System.out.println("---------------------------------------------------");
			    		  System.out.println("Results for weekdays with time between 7 am to 7 pm" );     
		        		System.out.println("---------------------------------------------------");
					      break;
				case 1: System.out.println("----------------------.----------------------------");
			   		   System.out.println("Results for weekdays with time between 7 pm to 7 am" );
		        		System.out.println("---------------------------------------------------");			
					      break;
				case 2: System.out.println("---------------------------------------------------");
			    		  System.out.println("                Results for weekends               ");
		        		System.out.println("---------------------------------------------------");
				      	break;
			}
			System.out.println("Precision@1 : " + ((double)numCorrect[m] / (double)(sequence_size[m]) * 100.0));
			System.out.println("Precision@5 : " + ((double)numCorrectTopFive[m] / (double)(sequence_size[m]) * 100.0));
			System.out.println("Precision@10 : " + ((double)numCorrectTopTen[m] / (double)(sequence_size[m]) * 100.0));
			System.out.println("Reciprocal rank : " + ((double)reciprocal[m] / (double)(sequence_size[m])));
			System.out.println("Percentage of correct that were different from previous : " + ((double)numPrevCorrect[m] / (double)(sequence_size[m]) * 100.0));
			System.out.println("Average distance correct : " + ((double)distanceCorrect[m] / (double)(numCorrect[m])));
			System.out.println("Average distance incorrect : " + ((double)distanceIncorrect[m] / (double)(sequence_size[m] - numCorrect[m])));
			System.out.println("Average distance overall : " + ((double)distanceOverall[m] / (double)(sequence_size[m])));
			System.out.println("Perplexity : " + Perplex[m]);
		}
		System.out.println("---------------------------------------------------");
		System.out.println("                   Overall Result                  ");
		System.out.println("---------------------------------------------------");
		int Sequence_size=0;
		int NumCorrect =0;
     		int NumCorrectTopFive =0;
     		int NumCorrectTopTen =0;
     		int NumPrev =0;
		int NumPrevCorrect =0;
		int RankPos = 0;
		double Reciprocal =0.0;
		double DistanceCorrect = 0.0;
		double DistanceIncorrect = 0.0;
		double DistanceOverall = 0.0;	
		double perplex=0.0;
		for(int m=0; m<3;m++) {
			Sequence_size+=sequence_size[m];
			NumCorrect+=numCorrect[m];
			NumCorrectTopFive+=numCorrectTopFive[m];
			NumCorrectTopTen+=numCorrectTopTen[m];
			NumPrev+=numPrev[m];
			NumPrevCorrect+=numPrevCorrect[m];
			RankPos+=rankPos[m];
			Reciprocal+=reciprocal[m];
			DistanceCorrect+=distanceCorrect[m];
			DistanceIncorrect+=distanceIncorrect[m];
			DistanceOverall+=distanceOverall[m];
			perplex+=Perplex[m];
		}
		System.out.println("Precision@1 : " + ((double)NumCorrect / (double)(Sequence_size) * 100.0));
		System.out.println("Precision@5 : " + ((double)NumCorrectTopFive / (double)(Sequence_size) * 100.0));
		System.out.println("Precision@10 : " + ((double)NumCorrectTopTen / (double)(Sequence_size) * 100.0));
		System.out.println("Reciprocal rank : " + ((double)Reciprocal / (double)(Sequence_size)));
		System.out.println("Percentage of correct that were different from previous : " + ((double)NumPrevCorrect / (double)(Sequence_size) * 100.0));
		System.out.println("Average distance correct : " + ((double)DistanceCorrect / (double)(NumCorrect)));
		System.out.println("Average distance incorrect : " + ((double)DistanceIncorrect / (double)(Sequence_size - NumCorrect)));	
		System.out.println("Average distance overall : " + ((double)DistanceOverall / (double)(Sequence_size)));
		System.out.println("Perplexity : " + perplex);
	        System.out.println("Area of the trixels : " + MyObservationVector.getArea(mm1[0].esym[0].toString())  + " km2" );
		System.out.println("Resolution : " + MyObservationVector.resolution);
		System.out.println("Maximum number of locations : " + maximumLength);
		System.out.println("Number of states : " + ( mm1[0] instanceof MM ? 1 : numStates));
  	        System.out.println("Number of sequences : "+maxSequences);
		System.out.println("Using model type : " + modelType);
 	 }
  
  	public static double perplexity ( List<List<MyObservationVector>> sequences, MarkovModel mm ) {
	    double perplexity = 0.0;
	    double size = 0.0;
	    for( int i=0; i< sequences.size(); i++ ) {
	     	Object [] x = new Object[sequences.get(i).size()];
	     	for ( int j = 0; j< sequences.get(i).size(); j++) { x[j] = sequences.get(i).get(j); }
	    	Forward fw = null;
		switch ( modelType ) {
			case HMM  : fw = new Forward((HMM)mm, x); break;
			case HMM2 : fw = new SecondOrderForward((SecondOrderHMM)mm, x); break; 
			case HMM3 : fw = new ThirdOrderForward((ThirdOrderHMM)mm, x); break; 
			case MM   : fw = new Forward((MM)mm, x); break;
			case MM2  : fw = new SecondOrderForward((SecondOrderMM)mm, x); break; 
			case MM3  : fw = new ThirdOrderForward((ThirdOrderMM)mm, x); break; 
		}
	    	double temp = fw.logprob();
	    	if (temp != 0) perplexity += temp;
	        size += sequences.get(i).size();
	    }
	    return Math.pow(Math.E, (-1.0 * perplexity / size));
  	}

  	public static Map<Object,Double> predictNext ( Object[] seq, MarkovModel mm ) {    
		Object[] seq2 = new Object[seq.length];
      		for ( int i=0; i < seq.length - 1 ; i++ ) seq2[i] = seq[i];  
		Map<Object,Double> map = new HashMap<Object,Double>();
      		for(int i = 0; i < mm.esym.length; i++) {
                  String trixel1 = mm.esym[i].toString();
                  if ( ((MyObservationVector)(seq2[seq2.length - 2])).distance(trixel1) < distanceThreshold ) {
                  	      seq2[seq2.length - 1] = new MyObservationVector(new double[]{MyObservationVector.getCatLatitude(trixel1), MyObservationVector.getCatLongitude(trixel1)});
			      Forward fw = null;
			      switch ( modelType ) {
				case HMM  : fw = new Forward((HMM)mm, seq2); break;
				case HMM2 : fw = new SecondOrderForward((SecondOrderHMM)mm, seq2); break; 
				case HMM3 : fw = new ThirdOrderForward((ThirdOrderHMM)mm, seq2); break; 
				case MM   : fw = new Forward((MM)mm, seq2); break;
				case MM2  : fw = new SecondOrderForward((SecondOrderMM)mm, seq2); break; 
				case MM3  : fw = new ThirdOrderForward((ThirdOrderMM)mm, seq2); break; 
			      }
       	   	              map.put(mm.esym[i], fw.logprob());
                  } else map.put(mm.esym[i], Double.NEGATIVE_INFINITY);
      		}
      		return map;  
  	}

}
